﻿<!DOCTYPE html>
<html>
<head>
  <title>Update Demo GoJS Sample</title>
  <!-- Copyright 1998-2021 by Northwoods Software Corporation. -->
  <meta name="description" content="Show the ChangedEvents that occur as the user modifies diagrams that share a single model, and the state of the UndoManager." />
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <script src="../release/go.js"></script>
  <script src="../assets/js/goSamples.js"></script>  <!-- this is only for the GoJS Samples framework -->
  <script id="code">
    function init() {
      if (window.goSamples) goSamples();  // init for these samples -- you don't need to call this
      var $ = go.GraphObject.make;  // for conciseness in defining templates

      blueDiagram =
        $(go.Diagram, "blueDiagram",
          {
            // double-click in background creates a new node there
            "clickCreatingTool.archetypeNodeData": { text: "node" }
          });

      blueDiagram.nodeTemplate =
        $(go.Node, "Auto",
          new go.Binding("location", "loc").makeTwoWay(),
          $(go.Shape, "RoundedRectangle",
            {
              fill: $(go.Brush, "Linear", { 0: "#00ACED", 0.5: "#00ACED", 1: "#0079A6" }),
              stroke: "#0079A6",
              portId: "", cursor: "pointer",  // the node's only port is the Shape
              fromLinkable: true, fromLinkableDuplicates: true, fromLinkableSelfNode: true,
              toLinkable: true, toLinkableDuplicates: true, toLinkableSelfNode: true
            }),
          $(go.TextBlock,
            { margin: 3, font: "bold 10pt Arial, sans-serif", stroke: "whitesmoke", editable: true },
            new go.Binding("text").makeTwoWay())
        );

      blueDiagram.linkTemplate =
        $(go.Link,
          {
            curve: go.Link.Bezier, adjusting: go.Link.Stretch,
            relinkableFrom: true, relinkableTo: true, reshapable: true
          },
          $(go.Shape,  // the link shape
            { strokeWidth: 2, stroke: "blue" }),
          $(go.Shape,  // the arrowhead
            {
              toArrow: "standard",
              fill: "blue", stroke: null
            })
        );


      greenDiagram =
        $(go.Diagram, "greenDiagram",
          {
            // double-click in background creates a new node there
            "clickCreatingTool.archetypeNodeData": { key: "node" }
          });

      greenDiagram.nodeTemplate =
        $(go.Node, "Vertical",
          new go.Binding("location", "loc").makeTwoWay(),
          $(go.Shape, "Ellipse",
            { fill: "lightgreen", width: 20, height: 20, portId: "" }),
          $(go.TextBlock,
            { margin: 3, font: "bold 12px Georgia, sans-serif" },
            new go.Binding("text"))
        );

      greenDiagram.linkTemplate =
        $(go.Link,
          $(go.Shape,  // the link shape
            { strokeWidth: 2, stroke: "#76C176" }),
          $(go.Shape,  // the arrowhead
            {
              toArrow: "standard",
              fill: "#76C176", stroke: null
            })
        );


      // create the model data that will be represented in both diagrams simultaneously
      var model = new go.GraphLinksModel(
        [
          { key: 1, text: "Alpha", loc: new go.Point(20, 20) },
          { key: 2, text: "Beta", loc: new go.Point(160, 120) }
        ],
        [
          { from: 1, to: 2 }
        ]);

      // the two Diagrams share the same model
      blueDiagram.model = model;
      greenDiagram.model = model;

      // now turn on undo/redo
      model.undoManager.isEnabled = true;


      // **********************************************************
      // A third diagram is on this page to display the undo state.
      // It functions as a tree view, showing the Transactions
      // in the UndoManager history.
      // **********************************************************

      var undoDisplay =
        $(go.Diagram, "undoDisplay",
          {
            allowMove: false,
            maxSelectionCount: 1,
            initialContentAlignment: go.Spot.TopLeft,
            layout:
              $(go.TreeLayout,
                {
                  alignment: go.TreeLayout.AlignmentStart,
                  angle: 0,
                  compaction: go.TreeLayout.CompactionNone,
                  layerSpacing: 16,
                  layerSpacingParentOverlap: 1,
                  nodeIndentPastParent: 1.0,
                  nodeSpacing: 0,
                  setsPortSpot: false,
                  setsChildPortSpot: false,
                  arrangementSpacing: new go.Size(2, 2)
                }),
            "animationManager.isEnabled": false
          });

      undoDisplay.nodeTemplate =
        $(go.Node,
          $("TreeExpanderButton",
            { width: 14, "ButtonBorder.fill": "whitesmoke" }),
          $(go.Panel, "Horizontal",
            { position: new go.Point(16, 0) },
            new go.Binding("background", "color"),
            $(go.TextBlock, { margin: 2 },
              new go.Binding("text", "text"))
          )
        );

      undoDisplay.linkTemplate = $(go.Link);  // not really used

      var undoModel =
        $(go.TreeModel,  // initially empty
          { isReadOnly: true });
      undoDisplay.model = undoModel;

      // ******************************************************
      // Add an undo listener to the main model
      // ******************************************************

      var changedLog = document.getElementById("modelChangedLog");
      var editToRedo = null; // a node in the undoDisplay
      var editList = [];

      model.addChangedListener(function(e) {
        // do not display some uninteresting kinds of transaction notifications
        if (e.change === go.ChangedEvent.Transaction) {
          if (e.propertyName === "CommittingTransaction" || e.modelChange === "SourceChanged") return;
          // do not display any layout transactions
          if (e.oldValue === "Layout") return;
        }  // You will probably want to use e.isTransactionFinished instead

        // Add entries into the log
        var changes = e.toString();
        if (changes[0] !== "*") changes = "&nbsp;&nbsp;" + changes;
        changedLog.innerHTML += changes + "<br/>";
        changedLog.scrollTop = changedLog.scrollHeight;

        // Modify the undoDisplay Diagram, the tree view
        if (e.propertyName === "CommittedTransaction") {
          if (editToRedo != null) {
            // remove from the undo display diagram all nodes after editToRedo
            for (var i = editToRedo.data.index + 1; i < editList.length; i++) {
              undoDisplay.remove(editList[i]);
            }
            editList = editList.slice(0, editToRedo.data.index);
            editToRedo = null;
          }

          var tx = e.object;
          var txname = (tx !== null ? e.object.name : "");
          var parentData = { text: txname, tag: e.object, index: editList.length - 1 };
          undoModel.addNodeData(parentData)
          var parentKey = undoModel.getKeyForNodeData(parentData);
          var parentNode = undoDisplay.findNodeForKey(parentKey);
          editList.push(parentNode);
          if (tx !== null) {
            var allChanges = tx.changes;
            var odd = true;
            allChanges.each(function(change) {
              var childData = {
                color: (odd ? "white" : "#E0FFED"),
                text: change.toString(),
                parent: parentKey
              };
              undoModel.addNodeData(childData)
              odd = !odd;
            });
            undoDisplay.commandHandler.collapseTree(parentNode);
          }
        } else if (e.propertyName === "FinishedUndo" || e.propertyName === "FinishedRedo") {
          var undoManager = model.undoManager;
          if (editToRedo !== null) {
            editToRedo.isSelected = false;
            editToRedo = null;
          }
          // Find the node that represents the undo or redo state and select it
          var nextEdit = undoManager.transactionToRedo;
          if (nextEdit !== null) {
            var itr = undoDisplay.nodes;
            while (itr.next()) {
              var node = itr.value;
              if (node.data.tag === nextEdit) {
                node.isSelected = true;
                editToRedo = node;
                break;
              }
            }
          }
        }
      }); // end model changed listener

      model.addChangedListener(function(e) {
        if (e.isTransactionFinished) {
          var tx = e.object;
          if (tx instanceof go.Transaction && window.console) {
            window.console.log(tx.toString());
            tx.changes.each(function(c) {
              if (c.model) window.console.log("  " + c.toString());
            });
          }
        }
      });
    } // end init

    function clearLog() {
      var div = document.getElementById("modelChangedLog").innerHTML = "";
    }
  </script>
</head>
<body onload="init()">
<div id="sample">
  <p>Update Demo <b>GoJS</b> Sample</p>
  <!-- The DIV for the Diagram needs an explicit size or else we won't see anything.
       Also add a border to help see the edges. -->
  <div style="width:100%; white-space:nowrap">
    <div style="display: inline-block; vertical-align: top; width:50%">
      <div id="blueDiagram" style="border: solid 1px blue; width:100%; height:300px;"></div>
      <div style="width:100%; height:20px"></div>
      <div id="greenDiagram" style="border: solid 1px green; width:100%; height:300px"></div>
    </div>
    <div style="display: inline-block; vertical-align: top; width:50%">
      <div style="width:100%; height:300px">
        <input type="button" onclick="clearLog()" style="height:20px; font-size: 11px;" value="Clear Model log" />
        <div id="modelChangedLog" style="height:280px;border: solid 1px gray; font-family:Monospace; font-size:11px; overflow:scroll"></div>
      </div>
      <div style="width:100%; height:20px"></div>
      <div>
        <input type="button" onclick="blueDiagram.commandHandler.undo()" style="height:20px; font-size: 11px;" value="Undo" />
        <input type="button" onclick="blueDiagram.commandHandler.redo()" style="height:20px; font-size: 11px;" value="Redo" />
        <div id="undoDisplay" style="height:280px; border: solid 1px gray"></div>
      </div>
    </div>
  </div>
  <p>
  This sample has two Diagrams, named "blueDiagram" and "greenDiagram", that display the same Model.
  Each diagram uses its own templates for its nodes and links, causing the appearance of each diagram to be different.
  However making a change in one diagram that changes the model causes those model changes to be reflected in the other diagram.
  </p>
  <p>
  This sample also shows, next to the blue diagram, almost all of the <a>ChangedEvent</a>s that the shared model undergoes.
  (For clarity it leaves out some of the Transaction-oriented events.)
  The model Changed listener adds a line for each ChangedEvent to the "modelChangedLog" DIV.
  Transaction notification events start with an asterisk "*",
  while property changes and collection insertions and removals start with an exclamation mark "!".
  </p>
  <p>
  Next to the green diagram there is a tree view display of the <a>UndoManager</a>'s history.
  The <a>UndoManager.history</a> is a <a>List</a> of <a>Transaction</a>s,
  where each <a>Transaction.changes</a> property holds all of the ChangedEvents that occurred due to some command or tool operation.
  These ChangedEvents are reflective of both changes to the model (prefixed with "!m") and to the diagram (prefixed with "!d").
  You will note that there are often several diagram changes for each model change.
  </p>
  <p>
  This demo is different from the <a href="twoDiagrams.html">Two Diagrams</a> sample, which is an example of two Diagrams,
  each sharing/showing a different <a>Model</a>, but sharing the same <a>UndoManager</a>.
  </p>
  <p>
  Many of the other samples demonstrate saving the whole model by calling <a>Model.toJson</a>.
  If you want to save incrementally, you should do so at the end of each transaction, when <a>ChangedEvent.isTransactionFinished</a>.
  The <a>ChangedEvent.object</a> may be a <a>Transaction</a>.
  Look through the <a>Transaction.changes</a> list for the model changes that you want to save.
  This code demonstrates the basic idea:
  </p>
  <pre>
  model.addChangedListener(function(e) {
    if (e.isTransactionFinished) {
      var tx = e.object;
      if (tx instanceof go.Transaction && window.console) {
            window.console.log(tx.toString());
        tx.changes.each(function(c) {
          // consider which ChangedEvents to record
          if (c.model) window.console.log("  " + c.toString());
        });
      }
    }
  });
  </pre>
</div>
</body>
</html>
